package com.a918.consensus;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

class DPoSPTNode extends Node {
    private final double timeout;
    private final Map<Integer, CycleState> cycleStates = new HashMap<>();
    private int cycle = 0;
    private double nextTimer;
    private Phase phase = Phase.PrePrepare;
    private CopyOnWriteArrayList<Block> blockChain = new CopyOnWriteArrayList<>();

    DPoSPTNode(EarthPosition position, double timeout) {
        super(position);
        this.timeout = timeout;
    }

    DPoSPTNode(EarthPosition position, double timeout, double credit, double token) {
        super(position,credit,token);
        this.timeout = timeout;
    }

    //开始共识
    @Override void onStart(Simulation simulation) {
        startProposal(simulation, 0);
    }

    @Override void onTimerEvent(TimerEvent timerEvent, Simulation simulation) {
        if (hasTerminated()) {
            // System.out.println("节点已达成共识");
            return;
        }else{
            // System.out.println("节点未达成共识");
        }
    }

    @Override void onMessageEvent(MessageEvent messageEvent, Simulation simulation) {
        if (hasTerminated()) {
            return;
        }

        Message message = messageEvent.getMessage();
        double time = messageEvent.getTime();

        if (message instanceof ProposalMessage) {
            handleProposalMessage(simulation, time, (ProposalMessage) message);
        } else if (message instanceof PrepareMessage) {
            handlePrepareMessage(simulation, time, (PrepareMessage) message);
        } else if (message instanceof CommitMessage) {
            handleCommitMessage(simulation, time, (CommitMessage) message);
        }
    }

    //添加新区块
    @Override
    public boolean addBlock(Block blcok){
        if (isValidNewBlock(blcok)) {
            blockChain.add(blcok);
            return true;
        }
//        System.out.println("添加新区块出错");
        return false;
    }

    private void handleProposalMessage(Simulation simulation, double time, ProposalMessage proposalMessage) {
        if(isValidProposal(proposalMessage) && phase == phase.PrePrepare) {
            getCycleState(proposalMessage.getCycle()).proposals.add(proposalMessage.getProposal());
            phase = Phase.Prepare;
            simulation.broadcast(this, new PrepareMessage(cycle, proposalMessage.getProposal()), time);
        }
    }


    private void handlePrepareMessage(Simulation simulation, double time,
                                      PrepareMessage prepareMessage) {
        if(phase == phase.PrePrepare || phase == phase.Prepare){
            CycleState currentCycleState = getCurrentCycleState();
            currentCycleState.addPrepareMessage(prepareMessage);
            Set<Proposal> prepareProposals = currentCycleState.getPrepareProposals(simulation);
            if (!prepareProposals.isEmpty() ) {
                phase = Phase.Commit;
                simulation.broadcast(this, new CommitMessage(cycle, prepareProposals.iterator().next()), time);
            }
        }

    }

    private void handleCommitMessage(Simulation simulation, double time,
                                     CommitMessage commitMessage) {
        if(phase != Phase.Reply){
            CycleState currentCycleState = getCurrentCycleState();
            currentCycleState.addCommitMessage(commitMessage);
            Set<Proposal> commitProposals = currentCycleState.getCommitProposals(simulation);
            if (!commitProposals.isEmpty()) {
                Proposal commitProposal = commitProposals.iterator().next();
                phase = Phase.Reply;
                simulation.broadcast(this, new CommitMessage(cycle, commitProposal), time);
                addBlock(commitMessage.getProposal().getBlock());
                terminate(commitProposal, time);
            }
        }

    }


    private void startProposal(Simulation simulation, double time) {
        if (equals(simulation.getCreator())) {
            Proposal proposal = new Proposal();
            proposal.setBlock(createNewBlock());
            Message message = new ProposalMessage(cycle, proposal);   //这里没有视图的变化，所以cycle恒为0
            simulation.broadcast(this, message, time);
        }
        resetTimeout(simulation, time);
    }

    private void resetTimeout(Simulation simulation, double time) {
        nextTimer = time + timeout;
        simulation.scheduleEvent(new TimerEvent(nextTimer, this));
    }

    private CycleState getCurrentCycleState() {
        return getCycleState(cycle);
    }

    private CycleState getLastCycleState() {
        return getCycleState(cycle - 1);
    }

    private CycleState getCycleState(int c) {
        cycleStates.putIfAbsent(c, new CycleState());
        return cycleStates.get(c);
    }

    private class CycleState {
        private Proposal startingValue = null;
        private Set<Proposal> proposals = new HashSet<>();
        private Map<Proposal, Integer> prepareMessageCounts = new HashMap<>();
        private Map<Proposal, Integer> CommitMessageCounts = new HashMap<>();

        void addPrepareMessage(PrepareMessage prepareMessage) {
            prepareMessageCounts.merge(prepareMessage.getProposal(), 1, Integer::sum);
        }

        void addCommitMessage(CommitMessage commitMessage) {
            CommitMessageCounts.merge(commitMessage.getProposal(), 1, Integer::sum);
        }


        Set<Proposal> getPrepareProposals(Simulation simulation) {
            return Util.keysWithMinCount(prepareMessageCounts, quorumSize(simulation));
        }

        Set<Proposal> getCommitProposals(Simulation simulation) {
            return Util.keysWithMinCount(prepareMessageCounts, quorumSize(simulation));
        }

    }

    private int quorumSize(Simulation simulation) {
        int nodes = simulation.getNetwork().getNodes().size();
        return nodes * 2 / 3 + 1;
    }

    private enum Phase {
        PrePrepare, Prepare, Commit, Reply
    }

    //创建区块
    public Block createNewBlock() {
        Block block = new Block();
        block.setIndex(blockChain.size());
        //时间戳
        block.setTimestamp(System.currentTimeMillis());
        block.setTransactions(new ArrayList<Transaction>());

        //上一区块的哈希
        block.setPreviousHash(Util.SHA256(blockChain.isEmpty()?"123":blockChain.get(blockChain.size()-1).toString()));
        //System.out.println("区块数:"+ blockChain.size()+";createBlock:"+block);
        return block;
    }

    public void resetPhase(){
        phase = Phase.PrePrepare;
        cycleStates.put(cycle, new CycleState());
    }


    private boolean isValidNewBlock(Block block){
        if(blockChain.isEmpty()){
            return true;
        }else {
            Block previousBlock = blockChain.get(blockChain.size()-1);
            if (!Util.SHA256(previousBlock.toString()).equals(block.getPreviousHash())) {
                //System.out.println("新区块的前一个区块hash验证不通过,上一个区块的hash应该为："+ Util.SHA256(previousBlock.toString()) +";这一个区块上一个区块hash为"+ block.getPreviousHash());
                return false;
            }
        }
        return  true;
    }

    private boolean isValidProposal(ProposalMessage proposalMessage){
        if(isValidNewBlock(proposalMessage.getProposal().getBlock())) {
            return true;
        }

        return false;
    }

    public Phase getPhase() {
        return phase;
    }

    public CopyOnWriteArrayList<Block> getBlockChain() {
        return blockChain;
    }

    public Block getLastBlock() {
        if(blockChain.isEmpty()) return null;
        return blockChain.get(blockChain.size()-1);
    }
}

